package com.xyzwps.server.paimon.annotation.processor

import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid

internal const val packageName = "com.xyzwps.server.paimon.gen.conf"
internal const val factoryClass = "RestApisFactory"
internal const val anno = "com.xyzwps.server.paimon.annotation.RestApis"

class RestApisSymbolProcessor(val env: SymbolProcessorEnvironment) : SymbolProcessor {

    override fun process(resolver: Resolver): List<KSAnnotated> {

        val symbols = resolver.getSymbolsWithAnnotation(anno).asIterable().toList()

        if (symbols.isEmpty()) {
            return emptyList()
        }

        for (symbol in symbols) {
            symbol.accept(RestApisClassVisitor(env), Unit)

        }

        val file = env.codeGenerator.createNewFile(Dependencies(true), packageName, factoryClass)

        file.write(
            """
            package $packageName
            
            import com.xyzwps.server.paimon.annotation.RestApis
            import org.slf4j.LoggerFactory
            import org.springframework.http.client.JdkClientHttpRequestFactory
            import org.springframework.web.client.RestClient
            import org.springframework.web.client.support.RestClientAdapter
            import org.springframework.web.service.invoker.HttpServiceProxyFactory
            import java.net.http.HttpClient
            import java.time.Duration
            import kotlin.reflect.KClass
            
            object $factoryClass {
            
                private val log = LoggerFactory.getLogger($factoryClass::class.java)
            
                fun <T : Any> create(c: KClass<T>, baseUrl: String): T {
                    log.info("[RestApis] init {}", c.qualifiedName)
                    return httpServiceProxyFactory(baseUrl).createClient(c.java)
                }
            
                private fun restClient(baseUrl: String): RestClient {
                    val connectTimeout = Duration.ofSeconds(5)
                    val readTimeout = Duration.ofSeconds(5)
                    val httpClient = HttpClient.newBuilder()
                        .version(HttpClient.Version.HTTP_1_1)
                        .connectTimeout(connectTimeout)
                        .build()
                    val httpRequestFactory = JdkClientHttpRequestFactory(httpClient)
                    httpRequestFactory.setReadTimeout(readTimeout)
                    return RestClient.builder()
                        .requestFactory(httpRequestFactory)
                        .baseUrl(baseUrl)
                        .build()
                }
            
                private fun httpServiceProxyFactory(baseUrl: String): HttpServiceProxyFactory {
                    val adapter = RestClientAdapter.create(restClient(baseUrl))
                    return HttpServiceProxyFactory.builderFor(adapter).build()
                }
            }
            """.trimIndent().toByteArray()
        )
        file.close()

        return emptyList()
    }
}

internal class RestApisClassVisitor(val env: SymbolProcessorEnvironment) : KSVisitorVoid() {
    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
        val originPackageName = classDeclaration.containingFile!!.packageName.asString()
        val originalClassName = classDeclaration.simpleName.asString()

        val annotation = getAnnotation(classDeclaration) ?: return
        val baseUrl = toString(annotation.arguments.first().value) ?: return

        val className = "${originalClassName}Configuration"
        val beanName = originalClassName.replaceFirstChar { c -> c.lowercaseChar() }

        val file = env.codeGenerator.createNewFile(Dependencies(true), packageName, className)

        file.write(
            """
            package $packageName
            
            import org.springframework.context.annotation.Bean
            import org.springframework.context.annotation.Configuration
            import $originPackageName.$originalClassName
            
            @Configuration
            class $className {
            
                @Bean
                fun ${beanName}(): $originalClassName {
                    return $factoryClass.create($originalClassName::class, "$baseUrl")
                }
            }
            """.trimIndent().toByteArray()
        )
        file.close()
    }

    private fun toString(value: Any?): String? = when (value) {
        null -> null
        is String -> value
        else -> null
    }


    private fun getAnnotation(classDeclaration: KSClassDeclaration): KSAnnotation? {
        for (annotation in classDeclaration.annotations) {
            val qualifiedName = annotation.annotationType.resolve().declaration.qualifiedName
            if (qualifiedName != null) {
                val annoPackageName = qualifiedName.getQualifier()
                val annoSimpleName = qualifiedName.getShortName()
                if ("$annoPackageName.$annoSimpleName" == anno) {
                    return annotation
                }
            }
        }
        return null
    }
}